package pushup.controller;

import java.util.ArrayList;
import java.util.Collection;

import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jface.viewers.TableViewer;
import org.xmonkey.notifier.SubscriptionsRegister;
import org.xmonkey.notifier.model.ISubscriber;

import pushup.model.Field;
import pushup.model.Statistics;
import pushup.model.Type;
import pushup.model.Package;
import pushup.model.Register;
import x_selectionlistener.ClassTabChangeEventDispatcher;
import x_selectionlistener.IClassTabChangeListener;

public class PushUpController implements ISubscriber, IClassTabChangeListener {
	
	private static final PushUpController instance = new PushUpController();
	
	private Register register = new Register();

	private TableViewer viewer;
	
	private PushUpController(){}
	
	public static PushUpController getInstance(){
		return instance;
	}
	
	public void subscribeToProject(IProject currentProject){
		register.reset();
		
		// I want to be notified on both start and end of the visit on the project
		SubscriptionsRegister.getInstance().registerStartNotificationRequestFor(currentProject, this);
		SubscriptionsRegister.getInstance().registerEndNotificationRequestFor(currentProject, this);
		
		// subscribe to some test nodes
		SubscriptionsRegister.getInstance().registerNotificationRequestFor(currentProject, ASTNode.TYPE_DECLARATION, this);
		SubscriptionsRegister.getInstance().registerNotificationRequestFor(currentProject, ASTNode.FIELD_DECLARATION, this);
		
		// ask x-monkey to start visiting the given project
		SubscriptionsRegister.getInstance().startVisitFor(currentProject);
		
		ClassTabChangeEventDispatcher.getInstance().addClassTabChangeListenerForProject(this, currentProject.getName());;
	}
	
	
	public void reactToVisit(IProject project, ASTNode node) {
		
		if(node.getNodeType() == ASTNode.TYPE_DECLARATION){
			
			TypeDeclaration typeDeclaration = (TypeDeclaration) node;
			ITypeBinding typeBinding = typeDeclaration.resolveBinding();
			
			Type type = register.getType(
					typeBinding.getPackage().getName(), typeBinding.getBinaryName(), typeBinding.getName());
			register.setCurrentType(type);
			type.setStub(false);
			
			ITypeBinding superclassTypeBinding = typeBinding.getSuperclass();
			if(superclassTypeBinding != null){
				Type superClass = register.getType(
						superclassTypeBinding.getPackage().getName(),
						superclassTypeBinding.getBinaryName(), superclassTypeBinding.getName());

				type.setSuperClass(superClass);
				superClass.addChildren(type);
			}
			
			try{
				CompilationUnit cu = (CompilationUnit)node.getRoot();
				type.setPath(cu.getJavaElement().getPath());
			} catch(Exception e){
				// Do nothing
			}
		}
		
		if(node.getNodeType() == ASTNode.FIELD_DECLARATION){
			FieldDeclaration fieldDeclaration = (FieldDeclaration) node;
			ITypeBinding typeBinding = fieldDeclaration.getType().resolveBinding();
			
			Object fragment = fieldDeclaration.fragments().get(0);
			String fieldName = "N/A";
			
			if(fragment instanceof VariableDeclarationFragment){
				fieldName = ((VariableDeclarationFragment) fragment).getName().toString();
				int startingOffset = ((VariableDeclarationFragment) fragment).getStartPosition();
				int length =((VariableDeclarationFragment) fragment).getLength();
				
				String packageName = Package.PRIMITVE_STUB_NAME; // default for primitive types
				
				if (typeBinding.getPackage() != null){
					packageName = typeBinding.getPackage().getName();
				}
				
				Type fieldType = register.getType(packageName, typeBinding.getBinaryName(),  typeBinding.getName());
				
				Field field = new Field(fieldType, fieldName, startingOffset, length);
				
				register.getCurrentType().addField(field);
			}
			
		}
		
	}

	public int getSubscriberOrder() {
		return 1000;
	}

	public String getSubscriberName() {
		return "PushUp";
	}

	public void reactToEnd(IProject project) {
		// I want to detach myself to ignore future notifications
		SubscriptionsRegister.getInstance().removeAllSubscriptions(project, this);
		
		// let us start a job that computes the similarities
		SimilaritiesJob similaritiesJob = new SimilaritiesJob(register);
		similaritiesJob.schedule();

	}

	public void reactToStart(IProject project) {
		System.out.println("X-Monkey will visit for me : " + project.getName());
	}

	public Collection<Type> getTypes() {
		return register.getTypes();
	}
	
	public Statistics getStatistics(){
		return register.getStatistics();
	}

	public void classTabBroughtToTop(String packageName, String className) {}

	public void classTabOpened(String packageName, String className) {}

	public void classTabClosed(String packageName, String className) {}

	public void classTabActivated(String packageName, String className) {
		ArrayList<Type> types =  getTypesWithSimilarities();
		
		// TODO use some caching with O(1)
		int counter = 0;
		for(counter = 0; counter < types.size(); counter++){
			String key = packageName + "." + className;
			String currentTypeName = types.get(counter).getName();
			
			if(currentTypeName.equals(key)){
				viewer.getTable().setSelection(counter);
				break;
			}
		}
		
	}

	public ArrayList<Type> getTypesWithSimilarities() {
		return register.getTypesWithSimilarities();
	}

	public void classTabDeactivated(String packageName, String className) {}

	public void setViewer(TableViewer viewer) {
		this.viewer = viewer;
	}


}
